// Copyright (c) 2009, Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package net.tawacentral.roger.secrets; import java.lang.reflect.Method; import android.Manifest; import android.app.Activity; import android.app.SearchManager; import android.content.Context; import android.content.pm.PackageManager; import android.util.Log; import android.view.InputDevice; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.InputMethodManager; /** * This class wraps OS-specific APIs behind Java's reflection API so that the * application can still run on OSs that don't support the given API. This * class is used as a backward compatibility layer so that I don't need to * build different versions of the application for different OSs. * * The root of the problem is the dalvik class verifier, which rejects classes * that depend on system classes or interfaces that are not present on the * device, causing the application to force exit. Instead of statically * depending on OS-specific classes, this code uses reflection to dynamically * discover if a class is available or not, in order to get around the verifier. * This is the "correct" way to support different OS versions. * * @author rogerta * */ public class OS { /** Tag for logging purposes. */ public static final String LOG_TAG = "OS"; /** Does the device support the Honeycomb (Android 3.0) APIs? */ public static boolean isAndroid30() { return android.os.Build.VERSION.SDK_INT >= 11; } /** Does the device support the Mashmellow (Android 6.0) APIs? */ public static boolean isAndroid60() { return android.os.Build.VERSION.SDK_INT >= 23; } /** Does secrets have permission to access external storage? */ public static boolean hasStoragePermission(Context ctx) { if (!isAndroid60()) return true; try { Method m = ctx.getClass().getMethod("checkSelfPermission", String.class); Integer ret = (Integer) m.invoke(ctx, Manifest.permission.WRITE_EXTERNAL_STORAGE); return ret == PackageManager.PERMISSION_GRANTED; } catch (Exception ex) { Log.e(LOG_TAG, "hasStoragePermission", ex); } return false; } /** * Check if secrets has external storage permission and request it if not. * * @param activity Activity that will use external storage. * @param code A request code to pass back to activities * onRequestPermissionsResult() method. * @return True storage is already granted. False is user will be asked * for permission. */ public static boolean ensureStoragePermission(Activity activity, int code) { if (hasStoragePermission(activity)) return true; try { Method m = activity.getClass().getMethod("requestPermissions", String[].class, int.class); m.invoke(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, code); } catch (Exception ex) { Log.e(LOG_TAG, "ensureStoragePermission", ex); } return false; } /** Hide the soft keyboard if visible. */ public static void hideSoftKeyboard(Context ctx, View view) { InputMethodManager manager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); if (null != manager) manager.hideSoftInputFromWindow(view.getWindowToken(), 0); } /** * Show the soft keyboard. * Does not seem to work on the emulator. */ public static void showSoftKeyboard(Context ctx, View view) { InputMethodManager manager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE); if (null != manager) manager.showSoftInput(view, 0); } /** * Invalidates the option menu so that it is recreated. This is needed * for Honeycomb so that the action bar can be updated when switching to * and from editing mode. * * @param activity The activity containing the option menu. */ public static void invalidateOptionsMenu(Activity activity) { if (!isAndroid30()) return; try { Method m = activity.getClass().getMethod("invalidateOptionsMenu"); m.invoke(activity); } catch (Exception ex) { Log.e(LOG_TAG, "invalidateOptionMenu", ex); } } /** * Configures a SearchView if this is Android 3.0 or later. * * @param activity The activity containing the search view. */ public static void configureSearchView(Activity activity, Menu menu) { if (!isAndroid30()) return; try { SearchManager sm = (SearchManager) activity.getSystemService( Context.SEARCH_SERVICE); if (null == sm) return; Method m = sm.getClass().getMethod("getSearchableInfo", android.content.ComponentName.class); Object si = m.invoke(sm, activity.getComponentName()); MenuItem item = menu.findItem(R.id.list_search); m = item.getClass().getMethod("getActionView"); View widget = (View) m.invoke(item); m = widget.getClass().getMethod("setSearchableInfo", si.getClass()); m.invoke(widget, si); } catch (Exception ex) { Log.e(LOG_TAG, "configureSearchView", ex); } } /** Does the device support a scroll wheel or trackball? */ public static boolean supportsScrollWheel() { int[] ids = InputDevice.getDeviceIds(); for (int id : ids) { InputDevice device = InputDevice.getDevice(id); int sources = device.getSources(); final int sourceTbOrDpad = InputDevice.SOURCE_TRACKBALL | InputDevice.SOURCE_DPAD; if (0 != (sources & sourceTbOrDpad)) return true; } return false; } }